#include "fyp/Camera.h"

//------------------------------------------------------------------------------
// additional header includes
#include "fyp/core/MatrixUtil.h"

//------------------------------------------------------------------------------
namespace ming
{
namespace fyp
{
	//--------------------------------------------------------------------------
	TCamera::TCamera() :
		mTargetPos(0, 0, 0),
		mTargetDist(2.0f),
		mYaw(0.0f),
		mPitch(Math::PI_OVER_4),
		mFov(Math::PI_OVER_4),
		mAspect(1.0f),
		mZNear(0.1f),
		mZFar(50.0f)
	{
		MarkProjParamDirty();
		MarkViewParamDirty();
	}
	
	//--------------------------------------------------------------------------
	TCamera::~TCamera()
	{
	}

	//--------------------------------------------------------------------------
	void TCamera::SetViewParams(float fov, float aspect, float zNear, float zFar)
	{
		mFov	= fov;
		mAspect	= aspect;
		mZNear	= zNear;
		mZFar	= zFar;

		MarkProjParamDirty();
	}

	//--------------------------------------------------------------------------
	void TCamera::SetTargetPosition(const TVec3 &pos, float distance)
	{
		mTargetPos = pos;
		mTargetDist = Math::Max(distance, mZNear);

		MarkViewParamDirty();
	}
	
	//--------------------------------------------------------------------------
	void TCamera::Yaw(float angle)
	{
		mYaw += angle;

		MarkViewParamDirty();
	}

	//--------------------------------------------------------------------------
	void TCamera::Pitch(float angle)
	{
		mPitch += angle;
		mPitch = Math::Min(mPitch,  Math::PI_OVER_2 - 0.001f);
		mPitch = Math::Max(mPitch, -Math::PI_OVER_2 + 0.001f);

		MarkViewParamDirty();
	}

	//--------------------------------------------------------------------------
	void TCamera::Forward(float dist)
	{
		mTargetDist += dist;
		mTargetDist = Math::Max(mTargetDist, mZNear);

		MarkViewParamDirty();
	}

	//--------------------------------------------------------------------------
	TMtx44 TCamera::GetViewMatrix()
	{
		UpdateViewMtx();

		return mViewMtx;
	}

	//--------------------------------------------------------------------------
	TMtx44 TCamera::GetProjMatrix()
	{
		UpdateProjMtx();

		return mProjMtx;
	}

	//--------------------------------------------------------------------------
	TVec3 TCamera::GetViewPosition()
	{
		UpdateViewMtx();

		return mViewPos;
	}
	
	//--------------------------------------------------------------------------
	TVec3 TCamera::GetViewDirection()
	{
		UpdateViewMtx();

		return mViewDir;
	}

	//--------------------------------------------------------------------------
	TVec4 TCamera::GetViewNearPlane()
	{
		UpdateViewMtx();

		TVec3 ptOnPlane = mViewPos + (mViewDir * mZNear);

		return TVec4(mViewDir[0], mViewDir[1], mViewDir[2], -dot(ptOnPlane, mViewDir) );
	}
	
	//--------------------------------------------------------------------------
	TVec4 TCamera::GetViewNearPlaneNormalized()
	{
		return GetViewNearPlane() / (mZFar - mZNear);
	}
	
	//--------------------------------------------------------------------------
	void TCamera::UpdateViewMtx()
	{
		if(mViewParamDirty)
		{
			TVec3 pitchDir, lookDir;

			pitchDir[0] = 0.0f;
			pitchDir[1] = Math::sin(mPitch);
			pitchDir[2] = -Math::cos(mPitch);

			// rotate the pitchDir around the Y-axis
			float cosYaw = Math::cos(mYaw);
			float sinYaw = Math::sin(mYaw);

			lookDir[0] = cosYaw * pitchDir[0] - sinYaw * pitchDir[2];
			lookDir[1] = pitchDir[1];
			lookDir[2] = sinYaw * pitchDir[0] + cosYaw * pitchDir[2];

			normalize(lookDir);

			mViewDir = -lookDir;

			mViewPos = mTargetPos + (lookDir * mTargetDist);

			mViewMtx = mtxLookAtLH( mViewPos, mTargetPos, TVec3(0, 1, 0) );
			
			mViewParamDirty = false;
		}
	}

	//--------------------------------------------------------------------------
	void TCamera::UpdateProjMtx()
	{
		if(mProjParamDirty)
		{
			mProjMtx = mtxPerspectiveFovLH(mFov, mAspect, mZNear, mZFar);

			mProjMtx[2][2] /= mZFar;
			mProjMtx[3][2] /= mZFar;

			mProjParamDirty = false;
		}
	}

	//--------------------------------------------------------------------------
	void TCamera::MarkViewParamDirty()
	{
		mViewParamDirty = true;
	}

	//--------------------------------------------------------------------------
	void TCamera::MarkProjParamDirty()
	{
		mProjParamDirty = true;
	}

	//--------------------------------------------------------------------------
}
}
